package	model.code;

import editor.ErrorHandler;
import model.code.line.LineList;
import model.code.line.SingleLine;
import model.code.line.ValueLine;
import model.symbol.Constant;
import model.symbol.Scope;
import model.symbol.Symbol;
import observer.ListObserver;
import observer.ObservedList;

import java.util.ArrayList;

/**
 * Instances of this class represent a set of related constants.
 * Enumerations can be created by setting the value of increment.
 * @author Neil Dickson
 */
public class Constants extends CodeFileChunk implements Symbol, ListObserver<Constant>, SingleLine.LineObserver {
	private	ObservedList<Constant>	constants;
	private ValueLine				increment;
	private	ObservedList<Rowable>	seeAlso;

	/**
	 * Indicates to have each subsequent constant be the previous shifted left by one bit
	 */
	public static final String INCREMENT_SHIFT_LEFT = "shl";
	/**
	 * Indicates to have each subsequent constant be the previous shifted right by one bit
	 */
	public static final String INCREMENT_SHIFT_RIGHT = "shr";

	/**
	 * Used for calculating value of first element if unspecified in case of an enumeration with integer increment.
	 */
	public static final String	DEFAULT_START_VALUE		= "0";
	/**
	 * Used for calculating value of first element if unspecified in case of an enumeration with shift left increment.
	 */
	public static final String	DEFAULT_SHL_START_VALUE	= "1";

	public static final char[]	DEFAULT_INCREMENT		= {};

	public Constants(String name, String description) {
		super(name, description);
		seeAlso = new ObservedList<Rowable>();
		constants = new ObservedList<Constant>();
		constants.addObserver(this);
		increment = new ValueLine(DEFAULT_INCREMENT,this,LOCATION_INCREMENT,0);
		increment.addObserver(this);
	}

	public void add(final Constant constant) {
		add(constant,constants.size());
	}
	public void add(final Constant constant,int index) {
		add(constant,index,true);
	}
	protected void add(final Constant constant,int index,boolean updateList) {
		if (updateList) {
			constants.add(index,constant,this);
		}
		Scope scope = getGlobalScope();
		if (scope!=null) {
			scope.add(constant);
		}
	}
	public Constant remove(int index) {
		return remove(constants.get(index),index,true);
	}
	protected Constant remove(Constant constant,int index,boolean updateList) {
		if (updateList) {
			constants.remove(index,this);
		}
		Scope scope = getGlobalScope();
		if (scope!=null) {
			scope.remove(constant);
		}
		return constant;
	}

	public void setSeeAlso(String[] seeAlso) {
		this.seeAlso = new ObservedList<Rowable>(seeAlso.length);
		for (String string : seeAlso) {
			this.seeAlso.add(Rowable.SEE_ALSO_DUMMY.copy(this,this.seeAlso.size(),new String[]{string}));
		}
	}

	public ObservedList<Rowable> getSeeAlso() {
		return seeAlso;
	}

	public ObservedList<Constant> getMembers() {
		return constants;
	}

	public ArrayList<Constant> getAllGlobalSymbols() {
		return constants;
	}

	public ValueLine getIncrement() {
		return increment;
	}

	public void listElementAdded(ObservedList<Constant> s, int index) {
		if (s==constants) {
			add(constants.get(index),index,false);
		}
	}

	public void listElementRemoved(ObservedList<Constant> s, int index, Constant element) {
		if (s==constants) {
			remove(element,index,false);
		}
	}

	public void listElementSet(ObservedList<Constant> s, int index, Constant oldElement) {
		if (s==constants) {
			ErrorHandler.showNotImplementedMessage("Sorry, there's an unimplemented function named Constants.listElementSet, and it was called, but it shouldn't have been.  The Constants was being notified that a Constant was set.  Hopefully this bug gets fixed in a later version.");
		}
	}

	public void listElementMoved(ObservedList<Constant> s, int from, int to) {
		if (s==constants) {
			if (isEnumeration()) {
				updateValues();
			}
		}
	}

	protected void updateValues() {
		// FIXME: Implement this once the values of constants are tokenized so that it's feasible to implement!!!
	}

	public Constants copy() {
		Constants that = new Constants(getName().toString(),getDescription().toString());
		that.seeAlso.ensureCapacity(seeAlso.size());
		for (Rowable sa : seeAlso) {
			that.seeAlso.add(sa.copy(that,that.seeAlso.size()));
		}
		// add() is used instead of = so that the elements get the necessary observers added
		// and that remains an observer of its content
		that.constants.ensureCapacity(constants.size());
		for (Constant constant : constants) {
			that.constants.add(constant.copy(that,that.constants.size()));
		}
		return that;
	}

	public boolean isEnumeration() {
		return increment.length()!=0;
	}

	public String getEnumeratedValue(Constant member) {
		if (!isEnumeration() || member.getValue().length()>0) {
			return member.getValue().toString();
		}
		int index = constants.indexOf(member);
		if (index==0) {
			if (increment.toString().equals(INCREMENT_SHIFT_LEFT)) {
				return DEFAULT_SHL_START_VALUE;
			}
			return DEFAULT_START_VALUE;
		}
		if (increment.toString().equals(INCREMENT_SHIFT_LEFT)) {
			return "("+constants.get(index-1).getName()+"*2)";
		}
		if (increment.toString().equals(INCREMENT_SHIFT_RIGHT)) {
			return "("+constants.get(index-1).getName()+"/2)";
		}
		return "("+constants.get(index-1).getName()+"+"+increment+")";
	}

	public void nowHaveGlobalScope() {
		for (Constant constant : constants) {
			constant.nowHaveGlobalScope();
		}
	}

	public LineList getLineList(int locType, int locIndex) {
		int row = locIndex>>>16;
		int col = locIndex&0xFFFF;
		ObservedList<? extends Rowable> table = null;
		if (locType==LOCATION_CONSTANTS_MEMBERS) {
			table = constants;
		}
		else if (locType==LOCATION_SEE_ALSO) {
			table = seeAlso;
		}
		else if (locType==LOCATION_INCREMENT) {
			return increment;
		}
		else {
			return super.getLineList(locType, locIndex);
		}
		if (table!=null && table.size()>row && table.get(row).getStrings().length>col) {
			return table.get(row).getStrings()[col];
		}
		return null;
	}

	public LineList getNextLineList(LineList list) {
		int locType = list.getLocationType();
		int locIndex = list.getLocationIndex();
		if (locType==LOCATION_NAME) {
			return description;
		}
		if (locType==LOCATION_DESCRIPTION) {
			return increment;
		}
		if (locType==LOCATION_INCREMENT) {
			if (constants.size()>0) {
				return getLineList(LOCATION_CONSTANTS_MEMBERS,0);
			}
			if (seeAlso.size()>0) {
				return getLineList(LOCATION_SEE_ALSO,0);
			}
			return null;
		}
		ObservedList<? extends Rowable> table = null;
		int row = locIndex>>>16;
		int col = locIndex&0xFFFF;
		if (locType==LOCATION_CONSTANTS_MEMBERS) {
			table = constants;
		}
		if (locType==LOCATION_SEE_ALSO) {
			table = seeAlso;
		}
		if (table!=null) {
			if (col+1<table.get(row).getSize()) {
				return getLineList(locType,locIndex+1);
			}
			if (row+1<table.size()) {
				return getLineList(locType,(row+1)<<16);
			}
			if (locType==LOCATION_CONSTANTS_MEMBERS && seeAlso.size()>0) {
				return getLineList(LOCATION_SEE_ALSO,0);
			}
		}
		return null;
	}
	public LineList getPrevLineList(LineList list) {
		int locType = list.getLocationType();
		int locIndex = list.getLocationIndex();
		if (locType==LOCATION_NAME) {
			return null;
		}
		if (locType==LOCATION_DESCRIPTION) {
			return name;
		}
		if (locType==LOCATION_INCREMENT) {
			return description;
		}
		ObservedList<? extends Rowable> table = null;
		int row = locIndex>>>16;
		int col = locIndex&0xFFFF;
		if (locType==LOCATION_CONSTANTS_MEMBERS) {
			table = constants;
		}
		if (locType==LOCATION_SEE_ALSO) {
			table = seeAlso;
		}
		if (table!=null) {
			if (col-1>=0) {
				return getLineList(locType,locIndex-1);
			}
			if (row-1>=0) {
				return getLineList(locType,(row-1)<<16);
			}
			if (locType==LOCATION_CONSTANTS_MEMBERS) {
				return increment;
			}
			return (constants.size()>0) ? constants.getLast().getStrings()[constants.getLast().getSize()-1] : increment;
		}
		return null;
	}

	public LineList getLastLineList() {
		if (seeAlso.size()>0) {
			return seeAlso.getLast().getStrings()[seeAlso.getLast().getSize()-1];
		}
		if (constants.size()>0) {
			return constants.getLast().getStrings()[constants.getLast().getSize()-1];
		}
		return increment;
	}

	public void valueChanged(SingleLine line, String oldValue) {
		if (line==increment) {
			// NOTE: The increment field has already been reparsed.
			for (Constant constant : constants) {
				// This forces the constant to be re-evaluated
				constant.evaluateIfNotAlready();
			}
		}
	}
}
